﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;

namespace MathExpression
{
    internal class Polynomial : IMathExpression
    {
        private readonly int _degree;

        public string Variable { get; private set; }

        public IExpressionData Data
        {
            get { return new PolynomialData(_values, Variable); }
        }

        internal ReadOnlyCollection<IMathExpression> Values
        {
            get { return _values.AsReadOnly(); }
        }

        private readonly List<IMathExpression> _values;

        public Polynomial(double freeValue, string variable = "x")
        {
            var scalar = new Scalar(freeValue);
            _values = new List<IMathExpression> {scalar};
            _degree = _values.Count;
            Variable = variable;
        }

        public Polynomial(double freeValue, double parameterValue, string variable = "x") : this(freeValue, variable)
        {
            _values.Add(new Scalar(parameterValue));
            _degree = _values.Count;
        }

        public Polynomial(IEnumerable<IMathExpression> polynom, string variable = "x")
        {
            _values = new List<IMathExpression>(polynom);
            _degree = _values.Count;
            Variable = variable;
        }

        private Polynomial(Polynomial value) : this(value.Values, value.Variable)
        {
        }

        public override bool Equals(object value)
        {
            if (!(value is Polynomial))
            {
                return false;
            }
            if (this == value)
            {
                return true;
            }
            var tableValue = (Polynomial) value;
            bool equalSize = _values.Count == tableValue._degree;
            if (equalSize)
            {
                ReadOnlyCollection<IMathExpression> polynom = tableValue.Values;
                for (int i = 0; i < _values.Count; i++)
                {
                    if (!_values[i].Data.Equals(polynom[i].Data))
                    {
                        return false;
                    }
                }
            }
            return true;
        }

        public bool Equals(Polynomial other)
        {
            if (ReferenceEquals(null, other)) return false;
            if (ReferenceEquals(this, other)) return true;
            return Equals(other._values, _values);
        }

        public override int GetHashCode()
        {
            return (_values != null ? _values.GetHashCode() : 0);
        }

        public IMathExpression Reverse()
        {
            throw new NotImplementedException();
        }

        public IMathExpression Add(IMathExpression expression)
        {
            if (expression is Scalar)
            {
                return Add(expression as Scalar);
            }
            if (expression is Polynomial)
            {
                return Add(expression as Polynomial);
            }
            return null;
        }

        private IMathExpression Add(Polynomial a)
        {
            int size = Math.Max(a._degree, _degree);
            var polynomial = new IMathExpression[size];
            for (int i = 0; i < size; i++)
            {
                if (i >= _degree)
                {
                    polynomial[i] = a.Values[i];
                }
                else if(i >= a._degree)
                {
                    polynomial[i] = _values[i];
                }
                else
                {
                    polynomial[i] = _values[i].Add(a.Values[i]);
                }
            }
            return new Polynomial(polynomial);
        }

        private IMathExpression Add(Scalar scalar)
        {
            var polynom = new List<IMathExpression>(_values);
            polynom[0] = polynom[0].Add(scalar);
            return new Polynomial(polynom, Variable);
        }

        public IMathExpression Subtract(IMathExpression expression)
        {
            if (expression is Scalar)
            {
                return Subtract(expression as Scalar);
            }
            if (expression is Polynomial)
            {
                return Subtract(expression as Polynomial);
            }
            return null;
        }

        private IMathExpression Subtract(Polynomial x)
        {
            int size = Math.Max(_degree, x._degree);
            var polynomial = new IMathExpression[size];
            for (int i = 0; i < size; i++)
            {
                polynomial[i] = _values[i].Subtract(x.Values[i]);
            }
            return new Polynomial(polynomial);
        }

        private IMathExpression Subtract(Scalar scalar)
        {
            var polynom = new List<IMathExpression>(_values);
            polynom[0] = polynom[0].Subtract(scalar);
            return new Polynomial(polynom, Variable);
        }

        public IMathExpression Multiply(IMathExpression expression)
        {
            if (expression is Scalar)
            {
                return Multiply(expression as Scalar);
            }
            if (expression is Polynomial)
            {
                var polynom = expression as Polynomial;
                if(polynom.Variable == Variable)
                {
                    return MultiplySimple(polynom);
                }
                return MultiplyDifferent(polynom);
            }
            return null;
        }

        public IMathExpression Divide(IMathExpression expression)
        {
            if (expression is Scalar)
            {
                return Divide(expression as Scalar);
            }
            return null;
        }

        private IMathExpression Divide(Scalar scalar)
        {
            var resultValues = new IMathExpression[_values.Count];
            for (int i = 0; i < _values.Count; i++)
            {
                resultValues[i] = _values[i].Divide(scalar);
            }
            return new Polynomial(resultValues);
        }

        private IMathExpression Multiply(Scalar scalar)
        {
            var polynom = new List<IMathExpression>(_values);
            for (int i = 0; i < polynom.Count; i++ )
            {
                polynom[i] = polynom[i].Multiply(scalar);
            }
            return new Polynomial(polynom, Variable);
        }

        private IMathExpression MultiplySimple(Polynomial x)
        {
            var firstValues = new ReadOnlyCollection<IMathExpression>(_values);
            ReadOnlyCollection<IMathExpression> secondValues = x.Values;
            Polynomial result = null;
            for (int i = 0; i < _degree; i++)
            {
                var polynom = new IMathExpression[i + x._degree];
                for (int j = 0; j < polynom.Length; j++)
                {
                    polynom[j] = new Scalar(0.0);
                }
                for (int j = 0; j < x._degree; j++)
                {
                    IMathExpression data = firstValues[i].Multiply(secondValues[j]);
                    polynom[i + j] = data;
                }
                var tableValue = new Polynomial(polynom);
                if (result == null)
                {
                    result = tableValue;
                }
                else
                {
                    result = result.Add(tableValue) as Polynomial;
                }
            }
            return result;
        }

        private IMathExpression MultiplyDifferent(Polynomial polynomial)
        {
            var polynom = new List<IMathExpression>(_values);
            for (int i = 0; i < _degree; i++)
            {
                polynom[i] = polynom[i].Multiply(polynomial);
            }
            return new Polynomial(polynom, Variable);
        }
        
        public IMathExpression Clone()
        {
            return new Polynomial(this);
        }
    }
}